﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Xml;
using System.Xml.Linq;

namespace Milan
{
	/// <summary>
	/// Settings can fall through:
	/// Settings.All[""] - Looks in User Product, then User Company, then in Product, then in Company and then in Version for the correct setting.
	/// </summary>
	public class Settings
	{
		public Settings(string fileName)
		{
			this.fileName = fileName;
		}
		private string fileName;
		/// <summary>
		/// Gets the list of values under the node which the xpath node.
		/// This is intended to be a special node which holds a list of values.
		/// </summary>
		/// <param name="xpath">the container node</param>
		/// <returns>list of values under the given node</returns>
		public List<string> GetValues(string xpath)
		{ 
			List<string> returns = new List<string>();
			var doc = new XmlDocument();
			try
			{
				var rootNode = GetSettingsNode(doc, xpath);
				XmlNodeList nodes = doc.SelectSingleNode("/settings/" + xpath).ChildNodes;
				foreach (XmlNode node in nodes)
				{
					returns.Add(node.InnerText);
				}
			}
			catch (Exception)
			{

			}
			return returns;
		}
		/// <summary>
		/// Gets a single value under a particular node
		/// </summary>
		/// <param name="xpath">Path to the node.</param>
		/// <param name="defaultValue">Default node if the value is missing.</param>
		/// <returns></returns>
		public string GetValue(string xpath, string defaultValue = null)
		{
			var doc = new XmlDocument();
			XmlNode node = GetSettingsNode(doc, xpath);
			if (node == null || node.InnerText == null || node.InnerText.Length == 0)
				return defaultValue;
			return node.InnerText;
		}
		/// <summary>
		/// Sets the value under the given node.
		/// </summary>
		/// <param name="xpath">An xpath object to the specific node.</param>
		/// <param name="value">The value to set the given node.</param>
		public void SetValue(string xpath, string value)
		{
			var doc = new XmlDocument();
			XmlNode node = GetSettingsNode(doc, xpath);
			node.InnerText = value;
			doc.Save(fileName);
		}
		/// <summary>
		/// Internal function to get the settings node.
		/// </summary>
		/// <param name="doc"></param>
		/// <param name="subNodeName"></param>
		/// <returns></returns>
		private XmlNode GetSettingsNode(XmlDocument doc, string subNodeName = null)
		{
			XmlNode settings = null;
			// Try to load the pre-existing file.
			try
			{
				doc.Load(fileName);
			}
			catch (Exception)
			{
			}
			settings = doc.SelectSingleNode("/settings");
			if (settings == null)
			{
				settings = doc.CreateElement("settings");
				doc.AppendChild(settings);
			}
			if (subNodeName != null)
			{
				var subNode = settings.SelectSingleNode(subNodeName);
				if (subNode == null)
				{
					subNode = doc.CreateElement(subNodeName);
					settings.AppendChild(subNode);
				}
				settings = subNode;
			}
			return settings;
		}
		/// <summary>
		/// Set all the values within the given xpath to the given list
		/// </summary>
		/// <param name="xpath">Path to the containing node</param>
		/// <param name="values">List of values to replace any other list at this point.</param>
		public void SetValues(string xpath, List<string> values)
		{
			var doc = new XmlDocument();
			XmlNode settings = GetSettingsNode(doc);
			// delete the children if it the root node exists
			XmlNode arrayHolder = doc.SelectSingleNode("/settings/" + xpath);
			if (arrayHolder == null)
			{
				arrayHolder = doc.CreateElement(xpath);
				settings.AppendChild(arrayHolder);
			}
			else
				arrayHolder.RemoveAll();
			foreach (var value in values)
			{
				var xmlElement = doc.CreateElement("item");
				xmlElement.InnerText = value;
				arrayHolder.AppendChild(xmlElement);
			}
			doc.Save(fileName);
		}
		public string this[string xpath]
		{
			get
			{
				return GetValue(xpath);
			}
			set
			{
				SetValue(xpath, value);
			}
		}
		/// <summary>
		/// User specific product settings
		/// </summary>
		public static Settings ProductUser
		{
			get
			{
				if (m_productUser == null)
				{
					string userDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), CompanyName, ProductName);
					System.IO.Directory.CreateDirectory(userDirectory);
					m_productUser = new Settings(Path.Combine(userDirectory, "settings.xml"));
				}
				return m_productUser;
			}
		}
		private static Settings m_productUser = null;
		/// <summary>
		/// Global Company Machine
		/// </summary>
		public static Settings CompanyMachine
		{
			get
			{
				if (m_companyMachine == null)
				{
					string appDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), CompanyName );
					System.IO.Directory.CreateDirectory(appDirectory);
					m_companyMachine = new Settings(Path.Combine(appDirectory, "settings.xml"));
				}
				return m_companyMachine;
			}
		}
		/// <summary>
		/// User Machine
		/// </summary>
		public static Settings CompanyUser
		{
			get
			{
				if (m_companyUser == null)
				{
					string appDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), CompanyName);
					System.IO.Directory.CreateDirectory(appDirectory);
					m_companyUser = new Settings(Path.Combine(appDirectory, "settings.xml"));
				}
				return m_companyUser;
			}
		}
		private static Settings m_companyUser;
		/// <summary>
		/// Global to this machine product settings.
		/// </summary>
		public static Settings ProductMachine
		{
			get
			{
				if (m_productMachine == null)
				{
					string appDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), CompanyName, ProductName);
					System.IO.Directory.CreateDirectory(appDirectory);
					m_productMachine = new Settings(Path.Combine(appDirectory, "settings.xml"));
				}
				return m_productMachine;
			}
		}
		private static Settings m_productMachine;
		/// <summary>
		/// Returns the id of this machine 
		/// </summary>
		public static Guid MachineId
		{
			get
			{
				if (m_machineId == new Guid())
				{
					string id = CompanyMachine.GetValue("MachineId");
					if (id == null)
					{
						id = Guid.NewGuid().ToString();
						CompanyMachine.SetValue("MachineId",id);
					}
					m_machineId = Guid.Parse(id);
				}
				return m_machineId;
			}
		}
		private static Guid m_machineId;

		private static Settings m_companyMachine = null;
		/// <summary>
		/// The name of the company in the entry assembly
		/// </summary>
		public static string CompanyName
		{
			get
			{
				if (companyName == null)
					companyName = ((AssemblyCompanyAttribute)Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(AssemblyCompanyAttribute), false)).Company;
				return companyName;
			}
		}
		private static string companyName = null;
		/// <summary>
		/// The name of the product in the entry assembly
		/// </summary>
		public static string ProductName
		{
			get
			{
				if (m_productName == null)
					m_productName = ((AssemblyProductAttribute)Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(AssemblyProductAttribute), false)).Product;
				return m_productName;
			}
		}
		private static string m_productName = null;
		/// <summary>
		/// UserData\CompanyName
		/// </summary>
		public static string UserCompanyFolder
		{
			get
			{
				if (m_userCompanyFolder == null)
				{
					m_userCompanyFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), CompanyName);
					System.IO.Directory.CreateDirectory(m_userCompanyFolder);
				}
				return m_userCompanyFolder;
			}
		}
		private static string m_userCompanyFolder = null;
		/// <summary>
		/// UserData\CompanyName\ProductName folder
		/// </summary>
		public static string UserProductFolder
		{
			get
			{
				if (m_userProductFolder == null)
				{
					m_userProductFolder = Path.Combine(UserCompanyFolder, ProductName);
					System.IO.Directory.CreateDirectory(m_userProductFolder);
				}
				return m_userProductFolder;
			}
		}
		private static string m_userProductFolder = null;
		/// <summary>
		/// Returns the ApplicationData\CompanyName folder
		/// </summary>
		public static string CompanyFolder
		{
			get
			{
				if (m_companyFolder == null)
				{
					m_companyFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), CompanyName);
					System.IO.Directory.CreateDirectory(m_companyFolder);
				}
				return m_companyFolder;
			}
		}
		private static string m_companyFolder = null;
		/// <summary>
		/// Returns the logging folder: ApplicationData\CompanyName\ProductName\vXX_XX_XX_XX\logs
		/// </summary>
		public static string LoggingFolder
		{
			get
			{
				if (m_loggingFolder == null)
				{
					m_loggingFolder = Path.Combine(ProductFolder,Milan.Deploy.Client.FormatVersionDirectory(ProductVersion),"logs");
				}
				return m_loggingFolder;
			}
		}
		private static string m_loggingFolder = null;
		/// <summary>
		/// Returns the ApplicationData\CompanyName\ProductName folder
		/// </summary>
		public static string ProductFolder
		{
			get
			{
				if (m_productFolder == null)
				{
					m_productFolder = Path.Combine(CompanyFolder, ProductName);
					System.IO.Directory.CreateDirectory(m_productFolder);
				}
				return m_productFolder;
			}
		}
		private static string m_productFolder = null;
		/// <summary>
		/// Returns the location of the this assembly, which are Usually in Program files\CompanyName\ProductName\vXX_XX_XX_XX
		/// </summary>
		public static string VersionFolder
		{
			get
			{
				if (m_versionFolder == null)
				{
					m_versionFolder = Path.GetDirectoryName(System.Reflection.Assembly.GetAssembly(typeof(Settings)).Location);
				}
				return m_versionFolder;
			}
		}
		private static string m_versionFolder = null;
		public static Deploy.ReleaseType ReleaseTypeSubscription
		{
			get
			{
				if (m_releaseTypeSubscription == Deploy.ReleaseType.Unknown)
				{
					m_releaseTypeSubscription = (Deploy.ReleaseType)Enum.Parse(typeof(Deploy.ReleaseType), Settings.ProductUser.GetValue("ReleaseType", "Unknown"));
					if(m_releaseTypeSubscription==Deploy.ReleaseType.Unknown)
						m_releaseTypeSubscription = (Deploy.ReleaseType)Enum.Parse(typeof(Deploy.ReleaseType), Settings.ProductMachine.GetValue("ReleaseType", "Official"));
				}
				return m_releaseTypeSubscription;
			}
		}
		private static Deploy.ReleaseType m_releaseTypeSubscription = Deploy.ReleaseType.Unknown;
		public static Version ProductVersion
		{
			get
			{
				if (m_currentVersion == null)
				{
					m_currentVersion = Assembly.GetEntryAssembly().GetName().Version;
				}
				return m_currentVersion;
			}
		}
		private static Version m_currentVersion;
		/// <summary>
		/// This represents the installed version.
		/// </summary>
		public static Version InstalledVersion
		{
			get
			{
				return Version.Parse(ProductMachine.GetValue("InstalledVersion", "0.0.0.0"));
			}
			set
			{
				ProductMachine.SetValue("InstalledVersion", value.ToString());
			}
		}
		public static string InstallProductDirectory
		{
			get
			{
				string returns = ProductMachine.GetValue("InstallDirectory");
				if ( returns == null)
				{
					returns = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),CompanyName,ProductName);
				}
				return returns;
			}
			set
			{
				ProductMachine.SetValue("InstallDirectory",value);
			}
		}
		public static string[] DownloadUrls
		{
			get
			{
				if (m_downloadUrls == null)
				{
					string urls = ((Milan.Reflection.AssemblyCommentAttribute)Attribute.GetCustomAttribute(Assembly.GetEntryAssembly(), typeof(Milan.Reflection.AssemblyCommentAttribute), false)).Comment;
					m_downloadUrls = urls.Split(',');
				}
				return m_downloadUrls;
			}
		}
		private static string[] m_downloadUrls;
		public static Milan.GUI.IHumanInterface GUI;	// For requesting not-found data
	}
}
